using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.SharePoint;
using Behemoth.SharePoint;
using Behemoth.SharePoint.CustomFieldTypes;
using Behemoth.SharePoint.LoggedCommentField.Data;

namespace Behemoth.SharePoint.LoggedCommentField {

  public class LoggedCommentFieldValue : XmlFieldValue {

    public LoggedCommentFieldValue() : base(string.Empty) {
      base.ClearSubColumns();
    }
    public LoggedCommentFieldValue( string multiColumnValue )
			: base( multiColumnValue ) {
		}
    public LoggedCommentFieldValue(object data, LoggedCommentField field)
      : base(string.Empty, string.Empty, data, 0) {
      if (data != null && data.GetType() != typeof(Comments))
        throw new Exception("You may only pass this constructor null or a data object of the type " + typeof(Comments).FullName);
      if (field != null) {
        PassFieldProperties(field);
        SyncDisplayValues();
      }
    }
    public LoggedCommentFieldValue(object data) : this(data, null) { }

    public Comments Comments {
      get {
        if (this.DataIsEmpty)
          return null;
        if (Data.GetType() != typeof(Comments))
          throw new Exception(string.Format("Data was of type {0} when only {1} type is supported.", Data.GetType(), typeof(Comments).FullName));
        return Data as Comments;
      }
    }

    public bool IsCommentsEmpty {
      get {
        if (this.DataIsEmpty)
          return true;
        return (Comments == null || Comments.Count <= 0);
      }
    }

    public override object CreateSerializableDataObject() {
      Comments c = new Comments();
      return c;
    }

    // TODO make it once again so that we can get these config values from the fieldName class instead
    private PresenceIconPlacementChoice _iconPlacement = PresenceIconPlacementChoice.BeforeLink;
    private CommentHistoryOrder _commentOrder = CommentHistoryOrder.RecentMessagesAtTop;
    private string _headerFormat = "<div style=\"font-weight:bold\">Comment by {0} at {1}</div>";
    private bool _editRichText = true;
    private int _numSummaryComments = 3;
    private bool _fieldPropsPassed = false;

    public void PassFieldProperties(LoggedCommentField field) {
      _fieldPropsPassed = true;
      _iconPlacement = field.PresenceIconPlacement;
      _commentOrder = field.HistoryDisplaySortOrder;
      _headerFormat = field.HeaderFormatRichText;
      _editRichText = field.EditRichText;
      _numSummaryComments = 3; // TODO implement this
    }

    public override void SyncDisplayValues() {
      if (!_fieldPropsPassed)
        throw new Exception("Called SyncDisplayValues() before PassFieldProperties().");
      string fullComments = string.Empty;
      string summaryComments = string.Empty;
      Comments c = this.Comments;
      int whenToStartSummary = c.Count - _numSummaryComments;
      int i = 0;
      foreach (Comment cmt in c) {
        // get the header
        string headerText = GenerateHeaderString(cmt);
        // append to the entire text
        fullComments = AppendCommentText(_commentOrder, _editRichText, headerText, fullComments, cmt.Text);
        if (i >= whenToStartSummary)
          summaryComments = AppendCommentText(_commentOrder, _editRichText, headerText, summaryComments, cmt.Text);
        i++;
      }
      // TODO add any custom buttons or whatever are needed around the outside of the comments HTML
      this.DisplayValueFull = fullComments;
      this.DisplayValueSummary = summaryComments;
    }

    #region String Manipulations for Comments HTML

    public string GenerateHeaderString(Comment cmt) {
      string headerText = string.Empty;
      try {
        SPWeb web = SPContext.Current.Site.RootWeb;
        headerText = GenerateHeaderString(this._headerFormat, this._iconPlacement, web, cmt.UserInfo.LoginName, cmt.TimeStamp);
      } catch (Exception ex) {
        headerText = GenerateHeaderString(this._headerFormat, cmt.UserNameWithPresence, cmt.TimeStamp);
      }
      return headerText;
    }
    public string GenerateHeaderString(string richTextFormat, PresenceIconPlacementChoice iconPlacement, SPWeb web, string userName, DateTime dt) {
      if (web == null)
        web = SPContext.Current.Site.RootWeb;
      SPUser user = web.Users[userName];
      return GenerateHeaderString(richTextFormat, iconPlacement, user, dt);
    }

    public string GenerateHeaderString(string richTextFormat, PresenceIconPlacementChoice iconPlacement, SPUser user, DateTime dt) {
      bool presenceAfter = iconPlacement == PresenceIconPlacementChoice.AfterLink;
      // following dt commented out becuase we figured out how to generate this pseudorandom id within the called function
      string userPresence = SPPresenceTools.GetUserWithPresence(user, presenceAfter/*, dt*/);
      return GenerateHeaderString(richTextFormat, userPresence, dt);
    }
    public string GenerateHeaderString(string richTextFormat, string userPresence, DateTime dt) {
      string header = string.Format(richTextFormat, userPresence, dt.ToString());
      return header;
    }

    // wrap comment nicely in tags and ensure non-richtext versions have line breaks
    private string WrapCommentText(string comment, bool isEditRichText) {
      if (!isEditRichText)
        comment = comment.Replace("\n", "<br />");
      comment = "<div class=\"LoggedCommentText\">" + comment + "</div>";
      return comment;
    }

    public string AppendCommentText(CommentHistoryOrder order, bool isEditRichText, string headerText, string currentText, string newText) {
      if (this.IsCommentEmpty(newText, isEditRichText))
        return currentText;
      string fmt = (order == CommentHistoryOrder.RecentMessagesAtTop) ? "{0}{1}{2}" : "{2}{0}{1}";
      string s = string.Format(fmt, headerText, WrapCommentText(newText, isEditRichText), currentText);
      return s;
    }

    #endregion

    #region Append Comments to Data

    public void AddComment(string comment) {
      AddComment(DateTime.Now, false, comment);
    }
    public void AddComment(SPUser user, string comment) {
      AddComment(user, DateTime.Now, false, comment);
    }
    public void AddComment(DateTime dt, bool isTimeUtc, string comment) {
      SPUser user = SPContext.Current.Web.CurrentUser;
      AddComment(user, dt, isTimeUtc, comment);
    }
    public void AddComment(SPFieldUserValue user, DateTime dt, bool isTimeUtc, string comment) {
      AddComment(user.User, dt, isTimeUtc, comment);
    }
    public void AddComment(SPUser user, DateTime dt, bool isTimeUtc, string comment) {
      if (!_fieldPropsPassed)
        throw new Exception("Called AddComment() before PassFieldProperties().");
      if (this.IsCommentEmpty(comment, this._editRichText))
        return;
      Comment c = new Comment();
      if (isTimeUtc)
        c.TimeStamp = dt;
      else
        c.TimeStampUtc = dt;
      c.ID = Guid.NewGuid().ToString();
      c.Text = comment;
      c.IsRichText = this._editRichText; 
      c.UserInfo.Sip = user.Email;
      c.UserInfo.LoginName = user.LoginName;
      c.UserInfo.ID = user.ID;
      c.UserInfo.DisplayName = user.Name;
      // following dt commented out becuase we figured out how to generate this pseudorandom id within the called function
      c.UserNameWithPresence = SPPresenceTools.GetUserWithPresence(user, _iconPlacement == PresenceIconPlacementChoice.AfterLink/*, dt*/);
      this.Comments.Add(c);
      this.SyncDisplayValues();
    }

    #endregion

    #region Tests for Empty Comments

    // TODO make richtext setting something embedded in the data structure

    /*
    public bool IsTextFieldEmpty(TextBox textField) {
      string s = textField.Text;
      return IsTextFieldEmpty(s);
    }
    */
    public bool IsCommentEmpty(Comment c, bool isRichText) {
      return IsCommentEmpty(c.Text, isRichText);
    }
    /// <summary>
    /// The purpose of this method is to test a string to see  if it is empty or
    /// contains data that we would generaly consider to be meaningless whitespace.
    /// Examples include &lt;DIV&gt;&lt;/DIV&gt; and other such nonsense. It will
    /// also test for strings containing only spaces or non-breaking spaces.
    /// </summary>
    /// <param name="text"></param>
    /// <returns></returns>
    public bool IsCommentEmpty(string text, bool isRichText) {
      if (string.IsNullOrEmpty(text) || string.Compare(text, "<DIV></DIV>", true) == 0)
        return true;
      if (!isRichText)
        return false;
      // Couldn't use XML document to get InnerText because the SharePoint API does
      // not return fully complioant XML: attributes don't have quotes.
      string xText = GetInnerText(text);
      xText = xText.Replace("&nbsp;", string.Empty);
      xText = xText.Trim();
      return (string.IsNullOrEmpty(xText));
    }

    /// <summary>
    /// Returns only the text that is not inside tags
    /// </summary>
    /// <remarks>
    /// There is probably some better way to improve this later with RegEx or something
    /// for example this will get really weird with non-richtext like "a &lt; b" or malformed 
    /// HTML like "&lt;div&gt;&gt;text&lt;/div&gt;", but for most cases this should get us the correct result.
    /// </remarks>
    /// <param name="xHtml"></param>
    /// <returns></returns>
    protected string GetInnerText(string xHtml) {
      string innerText = string.Empty;
      bool inTag = false;
      foreach (char c in xHtml.ToCharArray()) {
        if (inTag && c == '>') {
          inTag = false;
        } else if (!inTag) {
          if (c == '<')
            inTag = true;
          else
            innerText += c.ToString();
        }
      }
      return innerText;
    }

    #endregion

    internal static LoggedCommentFieldValue UpConvertMultiColumnValue(object value) {
      if (value == null)
        return null;
      if (value.GetType() == typeof(SPFieldMultiColumnValue))
        return new LoggedCommentFieldValue(value.ToString());
      else if (value.GetType() == typeof(string))
        return new LoggedCommentFieldValue(value.ToString());
      else if (value.GetType() != typeof(LoggedCommentFieldValue))
        throw new ArgumentException("Must be of type LoggedCommentFieldValue, SPFieldMultiColumnValue, or a parsable string.", "value");
      return value as LoggedCommentFieldValue;
    }

  } // class

} // namespace
